﻿using System;
using System.ComponentModel;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Represents a time signature.
    /// </summary>
    public class TimeSignature : EnvironmentModifier, IEquatable<TimeSignature>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeSignature"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that directly owns this time signature.
        /// </para>
        /// </param>
        /// <param name="presentedElement">
        /// <para>
        /// Type: <see cref="TimeSignatureElement"/>
        /// </para>
        /// <para>
        /// The presented time signature element.
        /// </para>
        /// </param>
        /// <param name="beat">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat to which the signature belongs.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="packagePart"/>, <paramref name="owner"/> or <paramref name="beat"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// The <paramref name="presentedElement"/> is <see langword="null"/>.
        /// </exception>
        internal TimeSignature(XmlPackagePart packagePart, Staff owner, TimeSignatureElement presentedElement, BeatElement beat)
            : base(packagePart, owner, presentedElement.FindOwner<MeasureElement>(), beat)
        {
            this.presentedElement = presentedElement;
            presentedElementAttribute.Value = presentedElement.ID.ToString();

            presentedElement.PropertyChanged += new PropertyChangedEventHandler(presentedElement_PropertyChanged);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeSignature"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner"><para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal TimeSignature(XmlPackagePart packagePart, Staff owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            presentedElementAttribute = element.Attributes[PackageVocabulary.IDRefAttribute];
            presentedElement = packagePart.Package.IDMap.FindObject<TimeSignatureElement>(new Guid(presentedElementAttribute.Value));
        }

        #endregion

        #region Object Members

        /// <summary>
        /// Determines whether the specified <see cref="Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="Object"/> to compare with this instance.</param>
        /// <returns>
        /// 	<see langword="true"/> if the specified <see cref="Object"/> is equal to this instance; otherwise, <see langword="false"/>.
        /// </returns>
        /// <exception cref="NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (obj is TimeSignature)
                return Equals(obj as TimeSignature);
            
            return base.Equals(obj);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion

        #region EnvironmentModifier Members

        /// <inheritdoc/>
        protected override XmlElement CreateSymbolElementCore(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.TimeSignatureElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            presentedElementAttribute = document.CreateAttribute(PackageVocabulary.IDRefAttribute);

            element.Attributes.Append(presentedElementAttribute);

            return element;
        }

        #endregion

        #region CoreSymbol Members

        /// <inheritdoc/>
        public override void ProcessEnvironmentChange(Environment environment)
        {
            
        }

        /// <inheritdoc/>
        public override double Width
        {
            get { return FindOwner<Staff>().SpaceSize * 2; }
        }

        #endregion

        #region IEquatable<TimeSignature> Members

        /// <inheritdoc/>
        public bool Equals(TimeSignature other)
        {
            if (other.SignatureType == SignatureType)
            {
                if (SignatureType == TimeSignatureType.Standard)
                    return Nominator == other.Nominator && Denominator == other.Denominator;
                else
                    return true;
            }
            else
                return false;
        }

        #endregion

        #region Operators

        /// <summary>
        /// Tests equality of two time signature.
        /// </summary>
        /// <param name="t">
        /// <para>
        /// Type: <see cref="TimeSignature"/>
        /// </para>
        /// <para>
        /// First time signature.
        /// </para>
        /// </param>
        /// <param name="s">
        /// <para>
        /// Type: <see cref="TimeSignature"/>
        /// </para>
        /// <para>
        /// Second time signature.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if the time signatures are equal; <see langword="false"/> otherwise.
        /// </para>
        /// </returns>
        public static bool operator ==(TimeSignature t, TimeSignature s)
        {
            if ((object)t == null)
                return (object)s == null;
            else if ((object)s == null)
                return false;

            return t.Equals(s);
        }

        /// <summary>
        /// Tests inequality of two time signature.
        /// </summary>
        /// <param name="t">
        /// <para>
        /// Type: <see cref="TimeSignature"/>
        /// </para>
        /// <para>
        /// First time signature.
        /// </para>
        /// </param>
        /// <param name="s">
        /// <para>
        /// Type: <see cref="TimeSignature"/>
        /// </para>
        /// <para>
        /// Second time signature.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if the time signatures are not equal; <see langword="false"/> otherwise.
        /// </para>
        /// </returns>
        public static bool operator !=(TimeSignature t, TimeSignature s)
        {
            return !(t == s);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when a property of the <see cref="PresentedElement"/> has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void presentedElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Beats")
                RaisePropertyChanged("Nominator");
            else if (e.PropertyName == "BeatUnit")
                RaisePropertyChanged("Denominator");
            else if (e.PropertyName == "SignatureType")
                RaisePropertyChanged("SignatureType");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the denominator of the time signature.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The denominator of the time signature.
        /// This value only has meaning if the <see cref="SignatureType"/>
        /// is <see cref="TimeSignatureType.Standard"/>.
        /// </para>
        /// </value>
        /// <seealso cref="TimeSignatureElement.BeatUnit"/>
        public ushort Denominator
        {
            get { return PresentedElement.BeatUnit; }
            set { PresentedElement.BeatUnit = value; }
        }

        /// <summary>
        /// Gets or sets the nominator of the time signature.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The nominator of the time signature.
        /// This value only has meaning if the <see cref="SignatureType"/>
        /// is <see cref="TimeSignatureType.Standard"/>.
        /// </para>
        /// </value>
        /// <seealso cref="TimeSignatureElement.Beats"/>
        public ushort Nominator
        {
            get { return PresentedElement.Beats; }
            set { PresentedElement.Beats = value; }
        }

        /// <summary>
        /// Gets the time signature composition element presented by this time signature.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="TimeSignatureElement"/>
        /// </para>
        /// <para>
        /// The time signature element presented by this time signature instance.
        /// </para>
        /// </value>
        public TimeSignatureElement PresentedElement
        {
            get { return presentedElement; }
        }

        /// <summary>
        /// Gets or sets the type of the time signature.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="TimeSignatureType"/>
        /// </para>
        /// <para>
        /// The type of the time signature.
        /// </para>
        /// </value>
        public TimeSignatureType SignatureType
        {
            get { return PresentedElement.SignatureType; }
            set { PresentedElement.SignatureType = value; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="PresentedElement"/> property.
        /// </summary>
        private TimeSignatureElement presentedElement;
        /// <summary>
        /// Holds the identifier of the <see cref="PresentedElement"/>.
        /// </summary>
        private XmlAttribute presentedElementAttribute;

        #endregion
    }
}
